DRY 原則:Don't Repeat Yourself
該原則的基本思想是,任何形式的重複程式碼都應該被消除,因為它們增加了維護的複雜性和風險。
– From 《Clean Code》
Day16 的文章中,我們觀察到在 Header 與 Sidebar 中存在了重複的搜尋功能邏輯。
其實我們在日常的開發過程中,經常會遇到重複或相似的功能邏輯。這不僅會增加維護的複雜度,還可能造成潛在的問題。
Vue 3 透過其強大的 Composition API 和 Composables,為我們提供了一個簡潔而強大的解決方案。
進一步,我們還可以整合 Apollo Client 與 Vue 3 的 Composition API,讓 GraphQL API 的結構更加清晰且方便管理。
Day18 開始前分支:feat/day_16/implement_search_function
Day18 進度完成分支:refactor/day_18/extract_search_composable
消除重複是 Clean Code 的核心原則之一,藉由減少不必要的程式碼重複,從而提升程式碼的易維護性和可讀性。
首先,我們應該敏銳地察覺到程式碼的壞味道 (Code Smell),並且及時思考這是否有潛在的問題。我們應該盡量選擇最佳的寫法,以減少未來的維護成本。
Code Smell: 程式碼中的壞味道,指向可能存在的潛在風險和應該改善之處。這些問題不一定會直接引發錯誤,但可能反映出設計上的不良之處,而這往往導致程式碼變得難以維護或拓展。
Duplication 被認為是一種 Code Smell。
簡單來說,如果在多個地方看到相同或非常相似的程式碼片段,這就是一個警告信號。
重複的程式碼意味著未來當開發者需要修改這部分邏輯時,可能需要在多處進行相同的更改,這增加了出錯的機會並降低了維護效率。因此,避免重複是提高程式碼品質的一個關鍵策略。
然而,當我們談論「重複」時,不僅僅是指完全相同的程式碼片段。
很多時候,程式碼雖然在表面上看起來是相似的,但它們所承載的業務邏輯和意義可能有很大的區別。
在這種情況下,正確地辨識這些相似性是非常關鍵的。過度的抽象化可能會讓程式碼變得難以理解和維護。有時候,容許一定程度的重複可以提高程式碼的可讀性,且為未來可能有所變化的業務邏輯提供更大的彈性。
我們的目標應該是寫出既不重複又易於維護的程式碼,而不僅僅是追求抽象的完美。
換句話說,DRY 原則並不是盲目消除所有的重複,而是一個思考如何更有效地組織和寫出高品質程式碼的方法。
在我們試圖將重複的部分獨立出來之前,必須進行深入的思考,包括但不限於:
Vue Composition API 是 Vue 3 提供的一套新功能,使開發者能夠更有組織地組合和重用功能邏輯。
跟 Option API 相比,Composition API 提供了一種方式,讓開發者可以在不依賴 Vue 實例選項(如 data、methods)的情況下,寫出組合性的程式碼。
模組化與重用: 允許開發者更容易地重用和組合功能邏輯,使得自定義的功能可以在多個組件之間共享。
清晰的組件結構: 讓相關的邏輯保持在一起,而不是基於選項 (如 data、methods、watch) 進行分散,使得程式碼更為組織化和可讀。
強化響應式: 使用 ref
和 reactive
強化了 Vue 的響應式能力,提供了更直觀的狀態管理和效能優化。
易於測試: 模組化的邏輯,讓測試特定的功能邏輯變得更加容易和直觀。
更好的 TypeScript 支援: 提供更好的 TypeScript 整合,使得開發者可以在編碼過程中得到更強大的類型推斷和檢查。
Composable 是 Vue 3 Composition API 的一部分,允許開發者封裝和重用邏輯功能。
透過 Composable,相關的功能和狀態可以被組織成獨立的模組,提供清晰且模組化的程式結構。此外,它還強化了資料的響應式能力,確保資料變動時的即時更新。
使用場景
重複的功能邏輯: 當多個組件有共享的邏輯時,例如資料提取、表單驗證等,可以將其放入 Composable。
非同步操作管理: 例如對 API 的呼叫,可以將相關的載入狀態、錯誤處理等封裝到 Composable。
複雜的狀態管理: 當組件內部的狀態變得複雜且需要一些相關的操作時,可以考慮使用 Composable 來進行組織。
整合第三方函式庫:若你要把 Vue 和其他第三方函式庫(像是 D3、Three.js)結合起來,可以利用 Composable 來處理這些交互的邏輯。
完整程式碼範例請參考:refactor/day_18/extract_search_composable
在 Header.vue
跟 Sidebar.vue
都有同樣的程式碼片段:
<script setup lang="ts">
// ...
const router = useRouter()
const searchTerm = ref('')
function handleSearch() {
if (searchTerm.value)
router.push({ name: 'search-results', query: { keyword: searchTerm.value } })
}
</script>
並且這個業務邏輯同樣都是文章搜尋的功能,這的確是一個 Duplication
的 Code Smell
讓我們將共用的邏輯移動到 Composable useSearch
內:src/composables/useSearch.ts
import type { Ref } from 'vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
export default function useSearch(): { searchTerm: Ref<string>; handleSearch: () => Promise<void> } {
const router = useRouter()
const searchTerm = ref('')
async function handleSearch() {
if (searchTerm.value) {
try {
await router.push({ name: 'search-results', query: { keyword: searchTerm.value } })
}
catch (error) {
console.error('Error navigating to search results:', error)
}
}
}
return {
searchTerm,
handleSearch,
}
}
將 handleSearch
改為非同步函式
值得注意的是,我們將 handleSearch
改為非同步函式,並在其內部使用 try/catch
來捕獲任何可能的錯誤。
這是因為在 Vue Router 4 中,router.push()
返回一個 Promise,而我們應該要妥善處理這個 Promise 的返回結果。
為什麼在 SFC 中可以不用處理呢?
在 Vue 3 以及 Vue Router 4 的 script setup
環境中,Vue 的錯誤處理機制會自動捕獲模板或 setup 函式的非同步錯誤。
然而,當我們將這段程式碼移至 composable
後,Vue 的錯誤處理機制不一定能自動捕獲使用時的非同步錯誤。
為了避免這種情況,最佳做法是始終處理 router.push()
返回的 Promise,無論它是在 script setup
中還是在 composable
中。
現在我們可以在 Header.vue
和 Sidebar.vue
中使用此 useSearch
:
<script setup lang="ts">
import { useSearch } from '@/composables/useSearch'
const { searchTerm, handleSearch } = useSearch()
</script>
如此一來,就輕鬆完成了模組化重構!
這樣做的好處是,如果搜尋功能邏輯在未來需要修改,我們只需要修改 useSearch.ts
。此外,重構後也明顯提升了 Component 程式碼的易維護性和可讀性。
我們在開發中常常用到的 Vue Apollo 的 useQuery
和 useMutation
,這兩者其實也是 composable 函式。它們是被設計在 Vue 3 的 Composition API 環境中使用,且可以與其他 composable 函式組合,創建更複雜的功能和邏輯。
我們可以通過將 GraphQL 查詢邏輯包裝在 composable 函式中,在不同組件中重用它,確保程式碼的 DRY (Don't Repeat Yourself) 和可維護性。
假設,我們有一個 取得使用者資料 的 Query
const { result, loading, error, refetch } = useQuery(gqlGetUserData);
以及一個 更新使用者資料 的 Mutation
const { mutate, loading, error } = useMutation(gqlUpdateUserData);
那麼我們可以組合 useQuery
和 useMutation
來實現 useUser
composable 函式:
function useUser() {
const { result: userData, refetch } = useQuery(gqlGetUserData);
const { mutate: updateUser } = useMutation(gqlUpdateUserData);
return {
userData,
refetchUserData: refetch,
updateUser
};
}
如此一來,就能輕鬆在 Component 中引入封裝好的 GraphQL API 邏輯,甚至我們可以把獨有的錯誤處理邏輯也一併封裝起來。
今日,我們探討了 DRY 原則與如何重構程式碼的重複與冗餘。我們必須要能正確地識別重複的程式碼片段,消除程式碼中的壞味道,重構寫出既不重複又易於維護的程式碼,而不僅僅是追求抽象的完美。
接著我們演練了如何透過 Vue 3 強大的 Composition API 和 Composables 模組化重構程式碼,透過封裝和重用邏輯,提升了程式碼的易維護性與可讀性。
最後進一步探討如何模組化和重用 GraphQL 查詢,暸解了 composable 允許我們在 Vue 3 的新環境中,更加模組化和靈活地組織 GraphQL 相關的邏輯和功能。
明天,我們將深入瞭解 CRUD 中的 Create 與 Delete,透過 GraphQL Mutation 來實際操作文章的新增與刪除,讓部落格應用有更豐富的互動性!